<?php
/**
* This is a library kit for webappkit.
* This is a PHP4 version only
* @link http://webappkit.net
* @package xpath
* @author J.Ducastel <jeremie@ducastel.name>
* @license http://opensource.org/licenses/gpl-license.php GNU Public License
*/

/**
* checks for dom_xml extension
*/
if (!function_exists("domxml_open_mem")) {
	trigger_error("dom_xml extension is required.",E_USER_WARNING);
	return;
}

/**
* Encapsulates a domNode and provides XPath syntax access methods.
* @uses domNode
* @author J.Ducastel <jeremie@ducastel.name>
* @todo setAttribute(), getAttribute()
*/
class xpathDomNode {
/*--------------------------------------------------------------------
CONSTRUCTOR
--------------------------------------------------------------------*/
	/**
	* constructor alias
	* @access public
	*/
	function xpathDomNode(&$src) {
		$this->__construct($src);
	}/**/
	
	/**
	* "real" constructor
	* @access public
	* @param mixed $src domNode or xml string
	*/
	function __construct(& $src) {
		// if source is a string, try to build from xml
		if (is_string($src)) {
			$dom=$this->_documentFromXml($src);
		} else if (is_a($src,'domNode')) {
			$dom=&$src;
		}
		// importing domNode if valid
		if (is_a($dom,'domnode')) {
			$this->node=&$dom;
			// setting owner document
			$this->doc=$this->node->owner_document();
			// setting xpath context
			$this->context=xpath_new_context($this->node);
			// setting namespaces
			$this->_setNamespaces();
		}
	}/**/
/*--------------------------------------------------------------------
PUBLIC STATIC METHODS
--------------------------------------------------------------------*/

/*--------------------------------------------------------------------
PUBLIC INSTANCE METHODS
--------------------------------------------------------------------*/
	/**
	* return mapped node's content as a string.
	* @access public
	* @return string
	*/
	function getContent() {
		return $this->node->get_content();
	}/**/

	/**
	* sets mapped node's content.
	* @param string $content
	* @access public
	* @return bool
	* @todo domNode insertion as child node
	*/
	function setContent(& $content) {
		return $this->node->set_content($content);
	}/**/

	/**
	* returns node's tagname
	* @access public
	* @return string
	*/
	function getTagName() {
		return $this->node->tagname();
	}/**/

	/**
	* return xml dump of the node
	* @param
	* @access public
	* @return string
	* /
	function getXml() {
		if (is_a($this->node,'domdocument'))
			return $this->node->dump_mem();
		return $this->node->dump_node($this->doc);
	}/**/
	
	/**
	* gets mapped node instance reference
	* @access public
	* @return domNode
	*/
	function & getNode() {
		return $this->node;
	}/**/
	

	/**
	* return node's owner document reference.
	* @access public
	* @return domDocument
	*/
	function & getDocument() {
		return $this->node->owner_document();
	}/**/
	
	/**
	 * return's document as an XML string
	 * @access public
	 * @param bool $indenting default true
	 * @param string $encoding default is 'UTF-8'
	 * @return string
	 */
	function getDocumentXml($indent=true,$encoding='UTF-8') {
		$doc=$this->node->owner_document();
		return $doc->dump_mem($indent,$encoding);
	}/**/

	/**
	* build, append and return node's child.
	* @param string $tagName tag name oif the node to create
	* @access public
	* @return ooXmlRpcDomNode created node
	*/
	function & makeChild($tagName) {
		// build dom node
		$doc=&$this->getDocument();
		$dom=$doc->create_element($tagName);
		//append domnode
		$this->node->append_child($dom);
		$node=&new ooXmlRpcDomNode($dom);
		return $node;
	}/**/
	
	/**
	* return targeted nodes.
	* @param string $xpath XPath targeting syntax
	* @access public
	* @return array domNode instances
	*/
	function searchNodes($xpath) {
		return $this->_collectXpath($xpath);
	}/**/
	
	/**
	* return targeted nodes content.
	* @param string $xpath XPath targeting syntax
	* @access public
	* @return array strings
	*/
	function searchContent($xpath) {
		$nodes=$this->_collectXpath($xpath);
		$results=array();
		for ($n=0; $n<count($nodes); $n++) {
			$results[]=$nodes[$n]->get_content();
		}
		return $results;
	}/**/

	/**
	* search nodes and replace them by passed value.
	* @access public
	* @param string $xpath XPath targeting syntax
	* @param mixed $value the value which will replace the targeted nodes
	* @return int affected nodes count
	*/
	function searchAndSetNodes($xpath,$value) {
		// getting targeted nodes
		$nodes=&$this->_collectXpath($xpath);
		// looping results
		$count=0;
		for ($n=0; $n<count($nodes); $n++) {
			//$results[]=$nodes[$n]->get_content();
			if ($this->_replaceNode($nodes[$n],$value))
				// success
				$count++;
		}
		return $count;
	}/**/
	
	/**
	* search nodes and set their content.
	* @access public
	* @param string $xpath XPath targeting syntax
	* @param mixed $value the value which will be applied to targeted nodes
	* @return int setted nodes
	*/
	function searchAndSetContent($xpath,$value) {
		// getting targeted nodes
		$nodes=&$this->_collectXpath($xpath);
		// looping results
		$count=0;
		for ($n=0; $n<count($nodes); $n++) {
			//$results[]=$nodes[$n]->get_content();
			if ($this->_replaceNodeContent($nodes[$n],$value))
				// success
				$count++;
		}
		return $count;
	}/**/
	
	/**
	 * removes selected nodes
	 * @access public
	 * @param string $xpath XPath targeting syntax
	 * @return int removed nodes count
	 */
	function searchAndRemoveNodes($xpath) {
		// getting targeted nodes
		$nodes=&$this->_collectXpath($xpath);
		// looping results
		$count=0;
		for ($n=0; $n<count($nodes); $n++) {
			if ($this->_removeNode($nodes[$n]))
				// success
				$count++;
		}
		return $count;
	}
	
	/**
	* get XPathContext instance
	* @access public
	* @return XPathContext
	*/
	function & getContext() {
		/*if (!$this->context and is_a($this->node,'domnode')) {
			$this->context=xpath_new_context($this->node);
		}*/
		return $this->context;
	}/**/
	
	/**
	* Registers a namespace's prefix
	* @param string $prefix
	* @param string $uri
	* @access public
	* @return bool
	*/
	function registerNamespace($prefix,$uri) {
		//$context=&$this->getContext();
		if (xpath_register_ns($this->context,$prefix,$uri)) {
			$this->namespaces[$prefix]=$uri;
			return true;
		}
		return false;
	}/**/
	
	/**
	* is a valid xml document file ?
	* @access public
	* @return bool
	*/
	function isValid() {
		return is_a($this->node,'domnode');
	}/**/
/*--------------------------------------------------------------------
VARS / MEMBERS
--------------------------------------------------------------------*/
	/**
	* @var domNode encapsulated dom node
	*/
	var $node;

	/**
	 * @var domDocument owner document
	 */
	var $doc;
	
	/**
	 * @var XPathContext
	 */
	var $context;
	
	/**
	 * @var array registered namespaces prefix => uri
	 */
	var $namespaces=array();
	
/*--------------------------------------------------------------------
PRIVATE / PROTECTED METHODS
--------------------------------------------------------------------*/
	/**
	* collects xpath-targeted elements
	* @param string $xpath
	* @access protected
	* @return array
	*/
	function & _collectXpath($xpath) {
		$results=array();
		// getting xpath context
		if ($context=&$this->getContext()) {
			$xO=xpath_eval($context,$xpath);
			if (is_a($xO,'xpathObject')) {
				switch ($xO->type) {
					case XPATH_NODESET:
						$results=$xO->nodeset;
						break;
					case XPATH_STRING:
						$results=array($xO->value);
						break;
					case XPATH_BOOLEAN:
					case XPATH_NUMBER:
					case XPATH_POINT:
					case XPATH_RANGE:
					case XPATH_LOCATIONSET:
					case XPATH_UNDEFINED:
					default:
						trigger_error("unkown node type",E_USER_WARNING);
						echo '<pre>'; print_r($xO);						
				}

			}
		}
		// echo $xpath;
		return $results;
	}/**/
	
	/**
	* will be called by __construct()
	* @access protected
	* @return bool
	*/
	function _setNamespaces() {
		return true;
	}/**/
	
	/**
	* tries to build a domDocument from an xml string
	* @access protected
	* @param string $xml xml string
	* @return xpathDomNode or false if failed
	*/
	function & _documentFromXml($xml) {
		// checking string start
		if (substr($xml,0,1)!='<') {
			$f=false; return $f;
		}
		// chargement en memoire
		$error=array();
		//$dom=@domxml_open_mem($xml);
		$dom=domxml_open_mem($xml,DOMXML_LOAD_PARSING,$error);
		if (!$dom) {
			// failed to build
			if (is_array($error)) {
				foreach ($error as $errorline) {    # Loop through all errors
					$txt=$errorline['errormessage']
					//." Node  : " . $errorline['nodename'] . "\n"
					." Line  : " . $errorline['line'] . "\n"
					." Column : " . $errorline['col'] . "\n\n";
					trigger_error($txt,E_USER_NOTICE);
				}
			}
			$f=false; return $f;
		}
		return $dom;
	}/**/
	
	/**
	 * converts passed value to a domNode for use
	 * also attaches it to the
	 **/
	function & _toNode(& $value) {
		switch (true) {
			case is_a($value,'domnode'):
				return $value;
			case is_string($value):
			case is_numeric($value):
				$node=&$this->doc->create_text_node(strval($value));
				// echo "<pre>$value =>"; print_r($node); echo '</pre>';
				return $node;
			case is_null($value):
				$node=&$this->doc->create_comment("NULL value inserted");
				return $node;
			default:
				// ???
				$type=gettype($value);
				$node=&$this->doc->create_comment("unsupported type ($type) for node conversion");
				return $node;
		}
	}

	/**
	* replaces a node by something else
	* @param domNode $node
	* @param mixed $new_content
	* @access protected
	* @return bool
	*/
	function _replaceNode(&$node, $new_content ) {
		$new_node=$this->_toNode($new_content);
		// print_r($new_node);
		$old=$node->replace_node($new_node);
		return is_a($old,'domnode');
	}/**/
	
	/**
	 * removes a node
	 * @param domNode $node
	 * @return bool
	 */
	function _removeNode(&$node) {
		$parent=&$node->parent_node();
		if ($parent->remove_child($node))
			return true;
		else
			return false;
	}
	
	/**
	* replaces a node's content
	* @param domNode $node
	* @param mixed $new_content
	* @access protected
	* @return bool
	*/
	function _replaceNodeContent(&$node, $new_content ) {
		//$dom = &$node->owner_document();
		$kids = &$node->child_nodes();
		foreach ( $kids as $kid )
			if ( $kid->node_type() == XML_TEXT_NODE )
				$node->remove_child ($kid);
		return $node->set_content($new_content);
	}/**/

	/**
	*
	* @param
	* @access protected
	* @return
	* /
	function () {

	}/**/
}
?>